package com.ajview.core.security.service;

import com.ajview.common.core.constant.Constants;
import com.ajview.common.core.domain.bo.LoginUser;
import com.ajview.common.core.domain.enums.ResultCodeEnum;
import com.ajview.common.core.exception.auth.TokenCheckException;
import com.ajview.common.utils.JwtUtils;
import com.ajview.common.utils.RedisUtils;
import com.ajview.common.utils.StringUtils;
import com.ajview.common.utils.uuid.IdUtils;
import com.ajview.core.config.model.JwtTokenModel;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.concurrent.TimeUnit;

/**
 * @author ZhangJunJie
 * @Date 2022-04-29
 * <p>
 * 概要：token生成服务
 */
@Component
@Slf4j
public class TokenService {

    @Resource
    private JwtTokenModel jwtTokenModel;
    @Resource
    private RedisUtils redisUtils;

    /**
     * token中存储的Map集合数据，通过指定的key获取val,拼接前缀从redis取出对应的User对象
     *
     * @param token： token
     * @return
     */
    public LoginUser getLoginUser(String token) {
        if (!StringUtils.isEmpty(token)) {
            //解析token
            try {
                Claims claims = JwtUtils.getClaims(token, jwtTokenModel.getSecret());
                String uuid = (String) claims.get(Constants.TOKEN_USER_KEY_PREFIX);
                // 获取在redis存储的key的值
                String tokenRedisKey = getTokenRedisKey(uuid);
                return (LoginUser) redisUtils.get(tokenRedisKey);
            } catch (Exception e) {
                log.error("Token解析错误，当前token为:{},异常信息为: {}", token, e.toString());
            }
        }
        return null;
    }

    /**
     * 生成的token 前加前缀。判断是否合法
     *
     * @param req
     * @return
     */
    public String getToken(HttpServletRequest req) throws TokenCheckException {
        String token = req.getHeader(jwtTokenModel.getHeader());
        // 看token是否携带我的指定前缀，否则不合法
        if (!StringUtils.isEmpty(token) && !token.startsWith(Constants.TOKEN_PREFIX)) {
            log.error("认证错误，认证参数不合法禁止访问，携带的token中前缀非法,token为: {} <-", token);
            throw new TokenCheckException(ResultCodeEnum.LOGIN_PARAMETER_WRONGFUL);
        }
        // 如果token不为空并且前缀是我的合法前缀，就将前缀移除得到一个符合token定义的token
        if (!StringUtils.isEmpty(token)) {
            return token.replace(Constants.TOKEN_PREFIX, "");
        }
        return null;
    }

    /**
     * 验证令牌有效期，相差不足20分钟，自动刷新缓存
     *
     * @param loginUser
     */
    public void verifyToken(LoginUser loginUser) {
        Long expireTime = loginUser.getExpireTime();
        long nowTime = System.currentTimeMillis();
        // 当令牌剩余时间小于设置的时间则自动续约
        if ((expireTime - nowTime) <= jwtTokenModel.getLimit()) {
            log.info("当前令牌剩余时间小于最大时间间隔正常续费,当前令牌剩余时间为{}", expireTime - nowTime);
            refreshToken(loginUser);
        } else {
            log.info("令牌剩余时间 {}，大于刷新令牌时间 {},不进行续费操作", expireTime - nowTime, jwtTokenModel.getLimit());
        }
    }

    /**
     * 获取到用户的唯一标识，存储下次过期时间到redis
     *
     * @param loginUser
     */
    private boolean refreshToken(LoginUser loginUser) {
        long nowTime = System.currentTimeMillis();
        //生成时间
        loginUser.setLoginTime(nowTime);
        //生成时间+默认过期时间 = 过期时间
        loginUser.setExpireTime(nowTime + jwtTokenModel.getExpirationMinute());
        // 为UUID添加前缀，当作redis的key 并存储到redis中
        String tokenRedisKey = getTokenRedisKey(loginUser.getTokenUUID());
        return redisUtils.set(tokenRedisKey, loginUser, jwtTokenModel.getExpirationMinute(), TimeUnit.MILLISECONDS);
    }

    /**
     * 生成token
     * 1. 生成本次登录会话的唯一uuid
     * 2. 将本次会话uuid与当前用户关联
     * 3. 存储并延长token过期时间，到Redis中
     * 4. 将本次会话的uuid，加密到token中，并将token返回
     *
     * @param loginUser
     * @return
     */
    public String createToken(LoginUser loginUser) {
        String uuid = IdUtils.simpleUUID();
        loginUser.setTokenUUID(uuid);
        boolean isToRedisToken = refreshToken(loginUser);
        if (isToRedisToken) {
            // 返回给前端的token 中key和
            HashMap<String, Object> claimsMap = new HashMap<>();
            // token中加密的uuid的key
            claimsMap.put(Constants.TOKEN_USER_KEY_PREFIX, uuid);
            return JwtUtils.createToken(claimsMap, jwtTokenModel.getSecret(), JwtUtils.SIGNATURE_ALGORITHM);
        } else {
            return null;
        }
    }

    /**
     * 根据redis的key删除redis中的用户信息
     *
     * @param uuid
     */
    public void delLoginUser(String uuid) {
        if (StringUtils.isNotEmpty(uuid)) {
            String uuidKey = getTokenRedisKey(uuid);
            redisUtils.del(uuidKey);
        }
    }


    /**
     * 给uuid添加前缀，从redis中获取
     *
     * @param uuid
     * @return
     */
    private String getTokenRedisKey(String uuid) {
        return Constants.TOKEN_USER_REDIS_KEY_PREFIX + uuid;
    }


}
